package com.newatlanta.commons.vfs.provider.gae.jar;

import java.io.IOException;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.VfsLog;
import org.apache.commons.vfs.provider.UriParser;

import com.newatlanta.commons.vfs.provider.gae.GaeFileSystem;

/**
 * 
 * @author bsorrentino
 *
 */
public class GaeJarFileSystem extends GaeFileSystem {
	private static final Log LOG = LogFactory.getLog(GaeJarFileSystem.class);

	private transient ZipInputStream jarInputStream;
	private transient java.util.Map<FileName,FileObject> _cache 
							= new java.util.HashMap<FileName,FileObject>(); 
	
	private final FileObject file;
	private Manifest mf;
	
	/**
	 * 
	 * @param rootName
	 * @param file
	 * @param fileSystemOptions
	 * @throws FileSystemException
	 */
	public GaeJarFileSystem(final FileName rootName, FileObject file,
			final FileSystemOptions fileSystemOptions)
			throws FileSystemException {
		super(rootName, fileSystemOptions);

		this.file = file;

	}

 
	/**
	 * 
	 */
	@Override
	protected void putFileToCache(FileObject file) {
		final FileName key = file.getName();
		//LOG.info( String.format("putFileToCache(%s)", key) );
		_cache.put(key, file);
	}

	/**
	 * 
	 */
	@Override
	protected FileObject getFileFromCache(FileName name) {
		return _cache.get( name );
	}

	protected void initExcludingManifestEntry() throws FileSystemException {
		
		try {
			if (jarInputStream == null) {

				jarInputStream = new JarInputStream(file.getContent().getInputStream(), true /*verify*/);

				mf = ((JarInputStream)jarInputStream).getManifest(); 
			}
			
			ZipEntry entry;
			while ((entry = ((JarInputStream)jarInputStream).getNextJarEntry()) != null) {
				
				final String entry_name = entry.getName();
				
				final FileName name = 
						getFileSystemManager()
							.resolveName(getRootName(), UriParser.encode(entry_name));

				// Create the file
				GaeJarFileObject fileObj;
				if (entry.isDirectory() && getFileFromCache(name) != null) {
					fileObj = (GaeJarFileObject) getFileFromCache(name);
					fileObj.setJarEntry(entry, jarInputStream);
					continue;
				}

				fileObj = createZipFileObject(name, entry, jarInputStream);			
				putFileToCache(fileObj);
				jarInputStream.closeEntry();
			}
		} catch (IOException e) {
			throw new FileSystemException(e);
		} finally {
			closeCommunicationLink();
		}
		
	}
	
	protected void initIncludingManifestEntry( ) throws FileSystemException {
		try {
			if (jarInputStream == null) {

				jarInputStream = new ZipInputStream(file.getContent().getInputStream());

			}
			
			ZipEntry entry;
			while ((entry = jarInputStream.getNextEntry()) != null) {
				
				final String entry_name = entry.getName();
				
				final FileName name = 
						getFileSystemManager()
							.resolveName(getRootName(), UriParser.encode(entry_name));

				// Create the file
				GaeJarFileObject fileObj;
				if (entry.isDirectory() && getFileFromCache(name) != null) {
					fileObj = (GaeJarFileObject) getFileFromCache(name);
					fileObj.setJarEntry(entry, jarInputStream);
					continue;
				}

				fileObj = createZipFileObject(name, entry, jarInputStream);			
				putFileToCache(fileObj);
				
				if( mf==null &&  "META-INF/MANIFEST.MF".contains(entry_name)) {
					
					mf = new Manifest( fileObj.getInputStream() );
				}
				jarInputStream.closeEntry();
			}
		} catch (IOException e) {
			throw new FileSystemException(e);
		} finally {
			closeCommunicationLink();
		}
		
	}
	/**
	 * 
	 */
	@Override
	public void init() throws FileSystemException {
		super.init();

		//initExcludingManifestEntry();
		initIncludingManifestEntry();

	}

	public Manifest getManifest() {
		return mf;
		
	}
	
	@Override
	protected void doCloseCommunicationLink() {
		// Release the zip file
		try {
			if (jarInputStream != null) {
				jarInputStream.close();
				jarInputStream = null;
			}
		} catch (final IOException e) {
			// getLogger().warn("vfs.provider.zip/close-zip-file.error :" +
			// file, e);
			VfsLog.warn(
					getLogger(),
					LOG,
					"vfs.provider.zip/close-zip-file.error :"
							+ super.getRootName(), e);
		}
	}

	@Override
	public synchronized FileObject resolveFile(FileName name) throws FileSystemException {
		return getFileFromCache(name);
	}

	@Override
	public FileObject resolveFile(String nameStr) throws FileSystemException {

		FileName name = getFileSystemManager().resolveName(getRootName(),
				UriParser.encode(nameStr));
		return this.resolveFile(name);
	}

	/**
	 * Creates a file object.
	 */
	@Override
	protected FileObject createFile(final FileName name) {
		throw new UnsupportedOperationException( "create file is not implemented!"); 
	}
	/**
	 * 
	 * @param name
	 * @param entry
	 * @param jis 
	 * @return
	 * @throws FileSystemException
	 */
	private GaeJarFileObject createZipFileObject(final FileName name,	final ZipEntry entry, ZipInputStream jis) throws FileSystemException {
		return new GaeJarFileObject(name, entry, this, jis, true);
	}
	


}
